Introduction + Purpose

For this project, I wanted to create a linear regression model for different population demographics by US location and respective air quality in that location. This was very interesting to me because I am very interested in environmental discrimination such as environmental racism. Therefore, I wanted to know if factors such as poverty, race, and gender affect the air quality in respective areas because this could potentially point to a symptom of environmental discrimination.

Data Description + Sources

The data for poverty was in the form of percentage of people in poverty by US county. The racial data was organized by white, black, hispanic, asian, and native american percentages by US county. The gender data was male percentages by US county. The air quality data was organized by number of days data was recorded, the number of good, moderate, unhealthy, very unhealthy, and hazardous data.

Because data was only available for all counties for 2010 for all of the variables I wanted in my model, that is the data I chose to use, even though it is not the most recent.

The race and poverty by county data is from the US Census’ Data Mapper tool.

The poverty data is from the US Census’ Small Area Income and Poverty Estimates tool.

The air quality data is from the EPA yearly Air Quality Index Report (https://www.epa.gov/outdoor-air-quality-data/air-quality-index-report).

Data Reformatting/Parsing

The air quality data contains features for the number of days the air quality was recorded and subsequently, the number of good, moderate, unhealthy, and hazardous air quality days. The number of days the quality was recorded for all of the counties is not necessarily the same. Therefore, the features for number of good, moderate, etc. days was converted to a percentage of number of days recorded. This was done in R before database storage.

# load the air quality data into a data frame with unnecessary data
airQuality <- read.csv("/Users/vikaba/Documents/Documents/northeastern/second_year/DS4100/final_project/annual_aqi_by_county_2010.csv")[,c(-3,-7,-11:-19)]
# convert all good, moderate, etc. day counts to percentages of days data was recorded
for (i in 1:nrow(airQuality)) {
  for (j in 4:8) {
    airQuality[i,j] = (airQuality[i,j]/airQuality[i,3])*100
  }
}
airQuality$State <- str_trim(as.character(airQuality$State))
airQuality$County <- str_trim(as.character(airQuality$County))
# export data back with the fixed air quality data
write.csv(airQuality, "AirQualityFixed.csv", row.names = FALSE)

The racial percentages were all in different tables. Before putting the data into the database, the race tables were combined into one table in Excel.

The racial, gender, and poverty tables all had “County” after the county names while the air quality data didn’t. In order to match up the data, “County” was deleted in those values. The poverty data also had state names as numbers and state initials in the county field. The initials were deleted and state numbers were converted to String names. This was all done in R before database storage

# load the race, gender, and poverty data into a data frame
race <- read.csv("/Users/vikaba/Documents/Documents/northeastern/second_year/DS4100/final_project/percent_race.csv")[-1915,] # row 1915 has spanish spelling
gender <- read.csv("/Users/vikaba/Documents/Documents/northeastern/second_year/DS4100/final_project/percent_male.csv")[-1915,] # row 1915 has spanish spelling
poverty <- read.csv("/Users/vikaba/Documents/Documents/northeastern/second_year/DS4100/final_project/percent_poverty.csv")
# delete all rows with states as Puerto Rico because the poverty data does not have Puerto Rico data
gender <- gender[gender$State != "Puerto Rico",]
race <- race[race$State != "Puerto Rico",]
# format race and gender data to not have "County" in the county name field
library(stringr)
gender$State <- as.character(gender$State)
gender$County <- as.character(gender$County)
race$State <- as.character(race$State)
race$County <- as.character(race$County)
for (i in 1:nrow(gender)) {
  gender[i,2] <- gsub("County", "", gender[i,2])
  gender[i,2] <- gsub("Borough", "", gender[i,2])
  gender[i,2] <- str_trim(gsub("Census Area", "", gender[i,2]))
  race[i,2] <- gsub("County", "", race[i,2])
  race[i,2] <- gsub("Borough", "", race[i,2])
  race[i,2] <- str_trim(gsub("Census Area", "", race[i,2]))
}
# format poverty data to not have state initials in county name and "County" and convert state numbers to state name
statesAbbs <- data.frame(state.abb, state.name)
statesAbbs$state.abb <- as.character(state.abb)
statesAbbs$state.name <- as.character(state.name)
poverty$State <- as.character(poverty$State)
poverty$County <- as.character(poverty$County)
for (i in 1:nrow(poverty)) {
  state <- unlist(strsplit(poverty[i,2], "[()]"))[2]
  poverty[i,1] <- statesAbbs[match(state, statesAbbs$state.abb),2]
  poverty[i,2] <- gsub("County", "", poverty[i,2])
  poverty[i,2] <- gsub("Census Area", "", poverty[i,2])
  poverty[i,2] <- gsub("Borough", "", poverty[i,2])
  getRidOfPars <- unlist(strsplit(poverty[i,2], "[(]"))[1]
  poverty[i,2] <- str_trim(getRidOfPars)
}
# export data back with the fixed race, gender, and poverty data
write.csv(race, "RaceFixed.csv", row.names = FALSE)
write.csv(gender, "GenderFixed.csv", row.names = FALSE)
write.csv(poverty, "PovertyFixed.csv", row.names = FALSE)

Database Storage

A relational SQL database was then created in my mySQL to store all values for air quality measurements and the demographic measurements by state.

The database has tables for poverty, race, gender, citizenship, and air quality.

# install and load package to connect to mySQL
#install.packages("RMySQL")
library(RMySQL)
# establish a connection with the local envi_model database
driver <- dbDriver("MySQL")
conn <-
  dbConnect(driver,
            user = "root",
            pass = "1234",
            dbname = "envi_model")

There are multiple values for each state in the air quality data. Therefore, these values were aggregated by averaging in mySQL before joining all of the tables to create a master table.

In order to create a predictive model, the tables in the database are all then joined to create a master table of demographics and air quality by state.

# aggregate the air quality table by state and average all of the values for each of the columns
# join all of existing measuring tables by state
query <-
  dbSendQuery(
    conn,
    statement = "SELECT DISTINCT
    air_quality.state,
    air_quality.county,
    poverty.poverty_percent,
    gender.male_percent,
    race.white_percent,
    race.black_percent,
    race.hispanic_percent,
    race.asian_percent,
    race.native_percent,
    air_quality.good_days,
    air_quality.moderate_days,
    air_quality.unhealthy_days,
    air_quality.very_unhealthy_days,
    air_quality.hazardous_days
FROM
    (SELECT DISTINCT * FROM gender) AS gender,
    (SELECT DISTINCT * FROM race) AS race,
    (SELECT DISTINCT * FROM poverty) AS poverty,
    (SELECT DISTINCT * FROM air_quality) AS air_quality
WHERE
    gender.state = gender.state AND
    gender.state = race.state
        AND gender.county = race.county
        AND race.state = poverty.state
        AND race.county = poverty.county
        AND poverty.state = air_quality.state
        AND poverty.county = air_quality.county
ORDER BY air_quality.state"
  )
allData <- fetch(query, n=-1)
write.csv(allData, "AllData.csv", row.names = FALSE)

Data Visualizations

Before creating a model for and evaluation the data, I used Tableau to create several visualizations that demonstrate some of the features by state and county as the data is interesting to visualize.

As can be seen from this visualization, the smaller circles which represent a low percentage of white people, are mostly darker colored than the bigger circles, meaning they have a higher poverty percentage.

Evaluating the Data

Outliers

First, outliers were identified in the response variable of good days by seeing which values were about 3 standard deviations from the mean.

Because this model deals with air quality data, I did not think it was necessary to omit these outliers. From looking at the outliers (of which there were only 8), the number of good days was low for those days because moderate or unhealthy days were high and I think that is important data to have. It also did not seem like these outliers were due to wrongful experimentation.

goodDaysMean <- mean(allData$good_days) # good days mean
goodDaysStDev <- sd(allData$good_days) # good days st dev
allData$stDevFromMean <- ((allData$good_days - goodDaysMean) / goodDaysStDev)
# find any values in good days that are 3 or more standard deviations from the mean
outliers <- allData[which(abs(allData$stDevFromMean) >= 3),]
outliers

Distribution

The response variable of good days was then analyzed for normal distribution with a histogram.

hist(allData$good_days)

A squared transform was then applied to the good days to normalize the good days variable.

# scatter plot of air quality good days data
hist((allData$good_days)^2)

# transform the average good days by square root
allDataGoodDaysNormalized <- allData[,c(-1,-2,-11:-15)]
allDataGoodDaysNormalized$good_days <- ((allData$good_days)^2)

ANOVA

ANOVA’s were done to see if certain features alone were enough to predict the percentage of good days for a location. These were done for male percentage, white percentage, and poverty percentage.

Then, ANOVA’s were done to see if these features with other features present were a significant predictor variable.

Male Percentage Only
# anova to see if male percentage alone is enough to predict number of good days
aov.genderOnly <- aov(formula = good_days ~ male_percent, data = allDataGoodDaysNormalized)
summary(aov.genderOnly)
               Df    Sum Sq   Mean Sq F value Pr(>F)    
male_percent    1 4.923e+08 492314114   88.19 <2e-16 ***
Residuals    1016 5.672e+09   5582325                   
---
Signif. codes:  0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1 ‘ ’ 1

Because the p value is less than 0.05, male percentage alone is enough to predict number of good days.

White Percentage Only
# anova to see if white percentage alone is enough to predict number of good days
aov.whiteOnly <- aov(formula = good_days ~ white_percent, data = allDataGoodDaysNormalized)
summary(aov.whiteOnly)
                Df    Sum Sq   Mean Sq F value   Pr(>F)    
white_percent    1 3.172e+08 317232571   55.13 2.39e-13 ***
Residuals     1016 5.847e+09   5754650                     
---
Signif. codes:  0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1 ‘ ’ 1

Because the p value is less than 0.05, white percentage alone is enough to predict number of good days.

Poverty Percentage Only
# anova to see if poverty percentage alone is enough to predict number of good days
aov.povertyOnly <- aov(formula = good_days ~ poverty_percent, data = allDataGoodDaysNormalized)
summary(aov.povertyOnly)
                  Df    Sum Sq  Mean Sq F value Pr(>F)
poverty_percent    1 1.602e+07 16018420   2.647  0.104
Residuals       1016 6.148e+09  6051120               

Because the p value is greater than 0.05, poverty percentage alone is not enough to predict number of good days.

All Percentages to See if Poverty, Male, and White Percentages Significant Predictors
# anova to see if poverty, white, and male percentages with other features are significant predictors
aov.goodDaysAll <- aov(formula = good_days ~ ., data = allDataGoodDaysNormalized)
summary(aov.goodDaysAll)
                   Df    Sum Sq   Mean Sq F value   Pr(>F)    
poverty_percent     1 1.602e+07  16018420   3.094 0.078907 .  
male_percent        1 4.818e+08 481782726  93.043  < 2e-16 ***
white_percent       1 2.070e+08 206963851  39.970 3.87e-10 ***
black_percent       1 1.299e+08 129863906  25.080 6.49e-07 ***
hispanic_percent    1 1.383e+07  13826362   2.670 0.102555    
asian_percent       1 5.903e+07  59025829  11.399 0.000763 ***
native_percent      1 2.665e+07  26653409   5.147 0.023492 *  
Residuals        1010 5.230e+09   5178042                     
---
Signif. codes:  0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1 ‘ ’ 1

The p values for male and white percentages are less than 0.05, making them significant predictors. The p value for poverty percentage is greater than 0.05, therefore it is not a significant predictor when all of the other features present

Scatter Plots

Scatter plots were also done to see if there was a linear regression relationship between gender, race, and poverty features and air quality numbers

Male Percentage vs Good Days
scatter.smooth(allDataGoodDaysNormalized$male_percent, allDataGoodDaysNormalized$good_days)

There does not seem to be a strong correlation between good days and percent of males. Good days seem to be constant when compared to percentage of males.

White Percentage vs Good Days
scatter.smooth(allDataGoodDaysNormalized$white_percent, allDataGoodDaysNormalized$good_days)

In this plot, the data seems scattered without a significant correlation between white percentage and good days.

Male Percentage vs Good Days
scatter.smooth(allDataGoodDaysNormalized$poverty_percent, allDataGoodDaysNormalized$good_days)

This plot also shows no significant correlation as the points are very scattered.

Spearman Rank and Pearson Moment Coefficients

Spearman and Pearson coefficients were also used to assess correlation between poverty, race, and gender percentages

Before the tests were performed on these features, an effort was made to normalize them for better correlation coefficient testing. No transforms were made on any of the data. The poverty data was relatively normally distributed. The race and gender data was skewed but did not respond well to transforms.

Pearson Moment Coefficient: percent male and number of good air quality days
cor(allDataGoodDaysNormalized$good_days, allDataGoodDaysNormalized$male_percent, method = "pearson")
[1] 0.2826125
Spearman Rank Coefficient: percent male and number of good air quality days
cor(allDataGoodDaysNormalized$good_days, allDataGoodDaysNormalized$male_percent, method = "spearman")
[1] 0.3291413
Pearson Moment Coefficient: percent white and number of good air quality days
cor(allDataGoodDaysNormalized$good_days, allDataGoodDaysNormalized$white_percent, method = "pearson")
[1] 0.2268606
Spearman Rank Coefficient: percent white and number of good air quality days
cor(allDataGoodDaysNormalized$good_days, allDataGoodDaysNormalized$white_percent, method = "spearman")
[1] 0.24137
Pearson Moment Coefficient: percent poverty and number of good air quality days
cor(allDataGoodDaysNormalized$good_days, allDataGoodDaysNormalized$poverty_percent, method = "pearson")
[1] -0.05097768
Spearman Rank Coefficient: percent poverty and number of good air quality days
cor(allDataGoodDaysNormalized$good_days, allDataGoodDaysNormalized$poverty_percent, method = "spearman")
[1] -0.06818472

For all of the features evaluated, the Spearman Rank and Pearson Moment coefficients are relatively close to each other. This means that outliers do not seem to be having an effect on the data. However, all of the coefficients are very low and significantly below 0.8. Because none of the coefficients are 0, there seems to be some correlation between these features and the number of good air quality days, but not enough to say that there is significant correlation. There is the least correlation between percent poverty and good air quality and the most correlation between percent of males and good air quality. None of these correlations, however, are strong.

The race and gender data is also not normally distributed which may be affecting the correlation coefficients. However, because the Spearman Rank and Pearson Moment coefficients for these 2 features were relatively close, the distribution does not seem to be significantly affecting the correlation coefficients

Building the Model(s)

The desired model for this dataset is a multiple linear regression model with some response variable dealing with the number of days of a certain air quality.

Model with Good Days as Response Variable

The first model has the good days as a response variable.

The data was first randomly split up 50-50 into a training and validation sets.

# training data set: random half of the data
training <- allDataGoodDaysNormalized[sample(nrow(allDataGoodDaysNormalized), nrow(allDataGoodDaysNormalized) / 2),]
# test data set: the other half of the data
test <- allDataGoodDaysNormalized[-c(as.numeric(rownames(training))),]

To create a multiple linear regression model, backward fitting was used and features with p values > 0.05 were removed at each step.

# linear regression model using training data and backward fitting, only keeping p values < 0.05
#goodDaysModel <- lm(formula = good_days ~ ., data = training)
goodDaysModel <- lm(formula = good_days ~ .-(hispanic_percent+poverty_percent+asian_percent+black_percent), data = training)
summary(goodDaysModel)

Call:
lm(formula = good_days ~ . - (hispanic_percent + poverty_percent + 
    asian_percent + black_percent), data = training)

Residuals:
    Min      1Q  Median      3Q     Max 
-6714.4 -1757.0    39.4  1700.9  6561.8 

Coefficients:
                 Estimate Std. Error t value Pr(>|t|)    
(Intercept)    -11948.969   2911.884  -4.104 4.74e-05 ***
male_percent      281.376     59.463   4.732 2.89e-06 ***
white_percent      41.570      6.594   6.304 6.33e-10 ***
native_percent     70.824     19.852   3.568 0.000395 ***
---
Signif. codes:  0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1 ‘ ’ 1

Residual standard error: 2263 on 505 degrees of freedom
Multiple R-squared:  0.1387,    Adjusted R-squared:  0.1336 
F-statistic: 27.12 on 3 and 505 DF,  p-value: 2.823e-16

Next, using the test data set was used to judge the accuracy of the model by calculating the prediction accuracy with the predicted values for the test data set. AIC and BIC calculations were also performed as they are good measures for model selection, in case other models are created for this data.

testUnnormalized <- test
testUnnormalized$good_days <- allData[-c(as.numeric(rownames(training))),c("good_days")]
goodDaysPreds <- predict(goodDaysModel, testUnnormalized)
# data frame of actual values vs predicted values
actuals_preds_good <- cbind(data.frame(actuals = testUnnormalized$good_days, predicteds = goodDaysPreds))
# prediction accuracy
correlation_accuracy <- cor(actuals_preds_good)
paste("Prediction Accuracy: ", correlation_accuracy[1,2] * 100, "%", sep="")
[1] "Prediction Accuracy: 38.6344403240153%"
# fit AIC and BIC calculations
paste("AIC:", AIC(goodDaysModel))
[1] "AIC: 9314.03660913136"
paste("BIC:", BIC(goodDaysModel))
[1] "BIC: 9335.19884921411"

Using the training and test data set to calculate the prediction accuracy, the model was correct about 39% of the time.

Model with Combo of Good and Moderate Days as Response Variable

To create this model, the sum of good and moderate days was taken to create the feature of acceptable air quality days.

allDataSumGoodModerate <- allData[,-15]
# create acceptable days feature
allDataSumGoodModerate$acceptable_days <- allData$good_days + allData$moderate_days
# only leave acceptable days feature in and omit out the rest of the air quality features
allDataSumGoodModerate <- allDataSumGoodModerate[,c(-1,-2,-10:-14)]

Then, outliers were identified again in the same method of standard deviation from the mean as the last model. Once again, I decided to leave these outliers in for more accurate data and because I believe this data is significant for analysis

acceptableDaysMean <- mean(allDataSumGoodModerate$acceptable_days) # acceptable days mean
acceptableDaysStDev <- sd(allDataSumGoodModerate$acceptable_days) # acceptable days st dev
# find any values in good days that are 3 or more standard deviations from the mean
allDataSumGoodModerate$stDevFromMean <- ((allDataSumGoodModerate$acceptable_days - acceptableDaysMean) / acceptableDaysStDev)
allDataSumGoodModerate[which(abs(allDataSumGoodModerate$stDevFromMean) >= 3),]
# omit the standard dev from mean columns for model creation
allDataSumGoodModerate <- allDataSumGoodModerate[,-9]

A histogram was then used to see the distribution of the response variable.

Because none of the transform had a significant effect on the distribution of the heavily right skewed data, the data was left as is.

hist(allDataSumGoodModerate$acceptable_days)

The data was then once again randomly split up 50-50 into training and test data sets.

# training data set: random half of the data
trainingAcceptableDays <- allDataSumGoodModerate[sample(nrow(allDataSumGoodModerate), nrow(allDataSumGoodModerate) / 2),]
# test data set: the other half of the data
testAcceptableDays <- allDataSumGoodModerate[-c(as.numeric(rownames(training))),]

Then, to create a multiple linear regression model, backward fitting was again used and features with p values > 0.05 were removed at each step.

# linear regression model using training data and backward fitting, only keeping p values < 0.05
acceptableDaysModel <- lm(formula = acceptable_days ~ .-(poverty_percent+male_percent), data = trainingAcceptableDays)
summary(acceptableDaysModel)

Call:
lm(formula = acceptable_days ~ . - (poverty_percent + male_percent), 
    data = trainingAcceptableDays)

Residuals:
    Min      1Q  Median      3Q     Max 
-72.472  -0.804   0.877   2.065  17.952 

Coefficients:
                 Estimate Std. Error t value Pr(>|t|)    
(Intercept)      45.66604    6.96454   6.557 1.36e-10 ***
white_percent     0.53732    0.07117   7.550 2.06e-13 ***
black_percent     0.54617    0.07372   7.408 5.44e-13 ***
hispanic_percent  0.12156    0.02705   4.493 8.70e-06 ***
asian_percent     0.55904    0.11933   4.685 3.61e-06 ***
native_percent    0.60591    0.08652   7.003 8.04e-12 ***
---
Signif. codes:  0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1 ‘ ’ 1

Residual standard error: 5.636 on 503 degrees of freedom
Multiple R-squared:  0.1406,    Adjusted R-squared:  0.1321 
F-statistic: 16.46 on 5 and 503 DF,  p-value: 4.642e-15

Next, using the test data set was used to judge the accuracy of the model by calculating the prediction accuracy with the predicted values for the test data set. AIC and BIC calculations were also performed as they are good measures for model selection, in case other models are created for this data.

acceptableDaysPreds <- predict(acceptableDaysModel, testAcceptableDays)
# data frame of actual values vs predicted values
actuals_preds_acceptable <- cbind(data.frame(actuals = testAcceptableDays$acceptable_days, predicteds = acceptableDaysPreds))
# prediction accuracy
correlation_accuracy <- cor(actuals_preds_acceptable)
paste("Prediction Accuracy: ", correlation_accuracy[1,2] * 100, "%", sep="")
[1] "Prediction Accuracy: 24.4240395426979%"
# fit AIC and BIC calculations
paste("AIC:", AIC(acceptableDaysModel))
[1] "AIC: 3212.77970365574"
paste("BIC:", BIC(acceptableDaysModel))
[1] "BIC: 3242.40683977159"

Using the training and test data set to calculate the prediction accuracy, the model was correct about 24% of the time.

Evalulation of Models

Model 1: Good Days as Response Variable

Overall, this model is not a very good measure for predicting the percentage of good air quality days a location will have. Both the multiple R squared and the adjusted R squared are very low and are significantly below 0.7. This means that the variation of good air quality days is not significantly explained by this model. The calculated MAD for this model is 28.66 which is not very close to 0, meaning there is a significant amount of deviation in this model. The model is statistically significant because the p-values for each feature as well as the overall model p-value are well below 0.05.

Model 2: Good+Moderate Days as Response Variable

This model is also not a great measure for predicting the percentage of acceptable days (that is, good and moderate days). The multiple R squared and the adjusted R sqaures are even lower in this model than the previous model. This means that even less variation in the acceptable air quality days is described by this model than the previous model. The calculated MAD for this model, however, is 0.074, which is much closer to 0 than the previous model. Meaning that there is much less deviation in this model than the previous model. This makes sense because outside of the 15 outliers, the percentage of acceptable days was mostly around 90%. This model is also statistically significant with overall and individual feature p-values well below 0.05. The AIC and BIC of this model are also much lower than the previous model, meaning this model is more likely to be the true model and is closer to the truth.

Conclusion

Overall, I hypothesized that building a model with these above demographic features would be a good predictor for air quality is US locations. However, from the two models I have built and the data I aggregated, this does not seem to be the case.

There are a few reasons why this was the result:

LS0tCnRpdGxlOiBEZW1vZ3JhcGhpY3MgYW5kIEFpciBRdWFsaXR5IGJ5IFVTIENvdW50aWVzCm91dHB1dDogaHRtbF9ub3RlYm9vawotLS0KCjxoMj4gSW50cm9kdWN0aW9uICsgUHVycG9zZSA8L2gyPgo8cD4gRm9yIHRoaXMgcHJvamVjdCwgSSB3YW50ZWQgdG8gY3JlYXRlIGEgbGluZWFyIHJlZ3Jlc3Npb24gbW9kZWwgZm9yIGRpZmZlcmVudCBwb3B1bGF0aW9uIGRlbW9ncmFwaGljcyBieSBVUyBsb2NhdGlvbiBhbmQgcmVzcGVjdGl2ZSBhaXIgcXVhbGl0eSBpbiB0aGF0IGxvY2F0aW9uLgpUaGlzIHdhcyB2ZXJ5IGludGVyZXN0aW5nIHRvIG1lIGJlY2F1c2UgSSBhbSB2ZXJ5IGludGVyZXN0ZWQgaW4gZW52aXJvbm1lbnRhbCBkaXNjcmltaW5hdGlvbiBzdWNoIGFzIGVudmlyb25tZW50YWwgcmFjaXNtLiBUaGVyZWZvcmUsIEkgd2FudGVkIHRvIGtub3cgaWYgZmFjdG9ycyBzdWNoIGFzIHBvdmVydHksIHJhY2UsIGFuZCBnZW5kZXIgYWZmZWN0IHRoZSBhaXIgcXVhbGl0eSBpbiByZXNwZWN0aXZlIGFyZWFzIGJlY2F1c2UgdGhpcyBjb3VsZCBwb3RlbnRpYWxseSBwb2ludCB0byBhIHN5bXB0b20gb2YgZW52aXJvbm1lbnRhbCBkaXNjcmltaW5hdGlvbi4gPC9wPgoKPGgyPiBEYXRhIERlc2NyaXB0aW9uICsgU291cmNlcyA8L2gyPgo8cD4gVGhlIGRhdGEgZm9yIHBvdmVydHkgd2FzIGluIHRoZSBmb3JtIG9mIHBlcmNlbnRhZ2Ugb2YgcGVvcGxlIGluIHBvdmVydHkgYnkgVVMgY291bnR5LiBUaGUgcmFjaWFsIGRhdGEgd2FzIG9yZ2FuaXplZCBieSB3aGl0ZSwgYmxhY2ssIGhpc3BhbmljLCBhc2lhbiwgYW5kIG5hdGl2ZSBhbWVyaWNhbiBwZXJjZW50YWdlcyBieSBVUyBjb3VudHkuIFRoZSBnZW5kZXIgZGF0YSB3YXMgbWFsZSBwZXJjZW50YWdlcyBieSBVUyBjb3VudHkuIFRoZSBhaXIgcXVhbGl0eSBkYXRhIHdhcyBvcmdhbml6ZWQgYnkgbnVtYmVyIG9mIGRheXMgZGF0YSB3YXMgcmVjb3JkZWQsIHRoZSBudW1iZXIgb2YgZ29vZCwgbW9kZXJhdGUsIHVuaGVhbHRoeSwgdmVyeSB1bmhlYWx0aHksIGFuZCBoYXphcmRvdXMgZGF0YS48L3A+CjxwPiBCZWNhdXNlIGRhdGEgd2FzIG9ubHkgYXZhaWxhYmxlIGZvciBhbGwgY291bnRpZXMgZm9yIDIwMTAgZm9yIGFsbCBvZiB0aGUgdmFyaWFibGVzIEkgd2FudGVkIGluIG15IG1vZGVsLCB0aGF0IGlzIHRoZSBkYXRhIEkgY2hvc2UgdG8gdXNlLCBldmVuIHRob3VnaCBpdCBpcyBub3QgdGhlIG1vc3QgcmVjZW50LiA8L3A+CjxwPiBUaGUgcmFjZSBhbmQgcG92ZXJ0eSBieSBjb3VudHkgZGF0YSBpcyBmcm9tIHRoZSBVUyBDZW5zdXMnIERhdGEgTWFwcGVyIHRvb2wuPC9wPgo8cD4gVGhlIHBvdmVydHkgZGF0YSBpcyBmcm9tIHRoZSBVUyBDZW5zdXMnIFNtYWxsIEFyZWEgSW5jb21lIGFuZCBQb3ZlcnR5IEVzdGltYXRlcyB0b29sLjwvcD4KPHA+IFRoZSBhaXIgcXVhbGl0eSBkYXRhIGlzIGZyb20gdGhlIEVQQSB5ZWFybHkgQWlyIFF1YWxpdHkgSW5kZXggUmVwb3J0IChodHRwczovL3d3dy5lcGEuZ292L291dGRvb3ItYWlyLXF1YWxpdHktZGF0YS9haXItcXVhbGl0eS1pbmRleC1yZXBvcnQpLjwvcD4KCjxoMj4gRGF0YSBSZWZvcm1hdHRpbmcvUGFyc2luZyA8L2gyPgpUaGUgYWlyIHF1YWxpdHkgZGF0YSBjb250YWlucyBmZWF0dXJlcyBmb3IgdGhlIG51bWJlciBvZiBkYXlzIHRoZSBhaXIgcXVhbGl0eSB3YXMgcmVjb3JkZWQgYW5kIHN1YnNlcXVlbnRseSwgdGhlIG51bWJlciBvZiBnb29kLCBtb2RlcmF0ZSwgdW5oZWFsdGh5LCBhbmQgaGF6YXJkb3VzIGFpciBxdWFsaXR5IGRheXMuIFRoZSBudW1iZXIgb2YgZGF5cyB0aGUgcXVhbGl0eSB3YXMgcmVjb3JkZWQgZm9yIGFsbCBvZiB0aGUgY291bnRpZXMgaXMgbm90IG5lY2Vzc2FyaWx5IHRoZSBzYW1lLiBUaGVyZWZvcmUsIHRoZSBmZWF0dXJlcyBmb3IgbnVtYmVyIG9mIGdvb2QsIG1vZGVyYXRlLCBldGMuIGRheXMgd2FzIGNvbnZlcnRlZCB0byBhIHBlcmNlbnRhZ2Ugb2YgbnVtYmVyIG9mIGRheXMgcmVjb3JkZWQuIFRoaXMgd2FzIGRvbmUgaW4gUiBiZWZvcmUgZGF0YWJhc2Ugc3RvcmFnZS4gPC9wPgpgYGB7cn0KIyBsb2FkIHRoZSBhaXIgcXVhbGl0eSBkYXRhIGludG8gYSBkYXRhIGZyYW1lIHdpdGggdW5uZWNlc3NhcnkgZGF0YQphaXJRdWFsaXR5IDwtIHJlYWQuY3N2KCIvVXNlcnMvdmlrYWJhL0RvY3VtZW50cy9Eb2N1bWVudHMvbm9ydGhlYXN0ZXJuL3NlY29uZF95ZWFyL0RTNDEwMC9maW5hbF9wcm9qZWN0L2FubnVhbF9hcWlfYnlfY291bnR5XzIwMTAuY3N2IilbLGMoLTMsLTcsLTExOi0xOSldCgojIGNvbnZlcnQgYWxsIGdvb2QsIG1vZGVyYXRlLCBldGMuIGRheSBjb3VudHMgdG8gcGVyY2VudGFnZXMgb2YgZGF5cyBkYXRhIHdhcyByZWNvcmRlZApmb3IgKGkgaW4gMTpucm93KGFpclF1YWxpdHkpKSB7CiAgZm9yIChqIGluIDQ6OCkgewogICAgYWlyUXVhbGl0eVtpLGpdID0gKGFpclF1YWxpdHlbaSxqXS9haXJRdWFsaXR5W2ksM10pKjEwMAogIH0KfQphaXJRdWFsaXR5JFN0YXRlIDwtIHN0cl90cmltKGFzLmNoYXJhY3RlcihhaXJRdWFsaXR5JFN0YXRlKSkKYWlyUXVhbGl0eSRDb3VudHkgPC0gc3RyX3RyaW0oYXMuY2hhcmFjdGVyKGFpclF1YWxpdHkkQ291bnR5KSkKCiMgZXhwb3J0IGRhdGEgYmFjayB3aXRoIHRoZSBmaXhlZCBhaXIgcXVhbGl0eSBkYXRhCndyaXRlLmNzdihhaXJRdWFsaXR5LCAiQWlyUXVhbGl0eUZpeGVkLmNzdiIsIHJvdy5uYW1lcyA9IEZBTFNFKQpgYGAKPHA+IFRoZSByYWNpYWwgcGVyY2VudGFnZXMgd2VyZSBhbGwgaW4gZGlmZmVyZW50IHRhYmxlcy4gQmVmb3JlIHB1dHRpbmcgdGhlIGRhdGEgaW50byB0aGUgZGF0YWJhc2UsIHRoZSByYWNlIHRhYmxlcyB3ZXJlIGNvbWJpbmVkIGludG8gb25lIHRhYmxlIGluIEV4Y2VsLiA8L3A+CjxwPiBUaGUgcmFjaWFsLCBnZW5kZXIsIGFuZCBwb3ZlcnR5IHRhYmxlcyBhbGwgaGFkICJDb3VudHkiIGFmdGVyIHRoZSBjb3VudHkgbmFtZXMgd2hpbGUgdGhlIGFpciBxdWFsaXR5IGRhdGEgZGlkbid0LiBJbiBvcmRlciB0byBtYXRjaCB1cCB0aGUgZGF0YSwgIkNvdW50eSIgd2FzIGRlbGV0ZWQgaW4gdGhvc2UgdmFsdWVzLiBUaGUgcG92ZXJ0eSBkYXRhIGFsc28gaGFkIHN0YXRlIG5hbWVzIGFzIG51bWJlcnMgYW5kIHN0YXRlIGluaXRpYWxzIGluIHRoZSBjb3VudHkgZmllbGQuIFRoZSBpbml0aWFscyB3ZXJlIGRlbGV0ZWQgYW5kIHN0YXRlIG51bWJlcnMgd2VyZSBjb252ZXJ0ZWQgdG8gU3RyaW5nIG5hbWVzLiBUaGlzIHdhcyBhbGwgZG9uZSBpbiBSIGJlZm9yZSBkYXRhYmFzZSBzdG9yYWdlIDwvcD4KYGBge3J9CiMgbG9hZCB0aGUgcmFjZSwgZ2VuZGVyLCBhbmQgcG92ZXJ0eSBkYXRhIGludG8gYSBkYXRhIGZyYW1lCnJhY2UgPC0gcmVhZC5jc3YoIi9Vc2Vycy92aWthYmEvRG9jdW1lbnRzL0RvY3VtZW50cy9ub3J0aGVhc3Rlcm4vc2Vjb25kX3llYXIvRFM0MTAwL2ZpbmFsX3Byb2plY3QvcGVyY2VudF9yYWNlLmNzdiIpWy0xOTE1LF0gIyByb3cgMTkxNSBoYXMgc3BhbmlzaCBzcGVsbGluZwpnZW5kZXIgPC0gcmVhZC5jc3YoIi9Vc2Vycy92aWthYmEvRG9jdW1lbnRzL0RvY3VtZW50cy9ub3J0aGVhc3Rlcm4vc2Vjb25kX3llYXIvRFM0MTAwL2ZpbmFsX3Byb2plY3QvcGVyY2VudF9tYWxlLmNzdiIpWy0xOTE1LF0gIyByb3cgMTkxNSBoYXMgc3BhbmlzaCBzcGVsbGluZwpwb3ZlcnR5IDwtIHJlYWQuY3N2KCIvVXNlcnMvdmlrYWJhL0RvY3VtZW50cy9Eb2N1bWVudHMvbm9ydGhlYXN0ZXJuL3NlY29uZF95ZWFyL0RTNDEwMC9maW5hbF9wcm9qZWN0L3BlcmNlbnRfcG92ZXJ0eS5jc3YiKQoKIyBkZWxldGUgYWxsIHJvd3Mgd2l0aCBzdGF0ZXMgYXMgUHVlcnRvIFJpY28gYmVjYXVzZSB0aGUgcG92ZXJ0eSBkYXRhIGRvZXMgbm90IGhhdmUgUHVlcnRvIFJpY28gZGF0YQpnZW5kZXIgPC0gZ2VuZGVyW2dlbmRlciRTdGF0ZSAhPSAiUHVlcnRvIFJpY28iLF0KcmFjZSA8LSByYWNlW3JhY2UkU3RhdGUgIT0gIlB1ZXJ0byBSaWNvIixdCgojIGZvcm1hdCByYWNlIGFuZCBnZW5kZXIgZGF0YSB0byBub3QgaGF2ZSAiQ291bnR5IiBpbiB0aGUgY291bnR5IG5hbWUgZmllbGQKbGlicmFyeShzdHJpbmdyKQpnZW5kZXIkU3RhdGUgPC0gYXMuY2hhcmFjdGVyKGdlbmRlciRTdGF0ZSkKZ2VuZGVyJENvdW50eSA8LSBhcy5jaGFyYWN0ZXIoZ2VuZGVyJENvdW50eSkKcmFjZSRTdGF0ZSA8LSBhcy5jaGFyYWN0ZXIocmFjZSRTdGF0ZSkKcmFjZSRDb3VudHkgPC0gYXMuY2hhcmFjdGVyKHJhY2UkQ291bnR5KQpmb3IgKGkgaW4gMTpucm93KGdlbmRlcikpIHsKICBnZW5kZXJbaSwyXSA8LSBnc3ViKCJDb3VudHkiLCAiIiwgZ2VuZGVyW2ksMl0pCiAgZ2VuZGVyW2ksMl0gPC0gZ3N1YigiQm9yb3VnaCIsICIiLCBnZW5kZXJbaSwyXSkKICBnZW5kZXJbaSwyXSA8LSBzdHJfdHJpbShnc3ViKCJDZW5zdXMgQXJlYSIsICIiLCBnZW5kZXJbaSwyXSkpCiAgcmFjZVtpLDJdIDwtIGdzdWIoIkNvdW50eSIsICIiLCByYWNlW2ksMl0pCiAgcmFjZVtpLDJdIDwtIGdzdWIoIkJvcm91Z2giLCAiIiwgcmFjZVtpLDJdKQogIHJhY2VbaSwyXSA8LSBzdHJfdHJpbShnc3ViKCJDZW5zdXMgQXJlYSIsICIiLCByYWNlW2ksMl0pKQp9CgojIGZvcm1hdCBwb3ZlcnR5IGRhdGEgdG8gbm90IGhhdmUgc3RhdGUgaW5pdGlhbHMgaW4gY291bnR5IG5hbWUgYW5kICJDb3VudHkiIGFuZCBjb252ZXJ0IHN0YXRlIG51bWJlcnMgdG8gc3RhdGUgbmFtZQpzdGF0ZXNBYmJzIDwtIGRhdGEuZnJhbWUoc3RhdGUuYWJiLCBzdGF0ZS5uYW1lKQpzdGF0ZXNBYmJzJHN0YXRlLmFiYiA8LSBhcy5jaGFyYWN0ZXIoc3RhdGUuYWJiKQpzdGF0ZXNBYmJzJHN0YXRlLm5hbWUgPC0gYXMuY2hhcmFjdGVyKHN0YXRlLm5hbWUpCnBvdmVydHkkU3RhdGUgPC0gYXMuY2hhcmFjdGVyKHBvdmVydHkkU3RhdGUpCnBvdmVydHkkQ291bnR5IDwtIGFzLmNoYXJhY3Rlcihwb3ZlcnR5JENvdW50eSkKZm9yIChpIGluIDE6bnJvdyhwb3ZlcnR5KSkgewogIHN0YXRlIDwtIHVubGlzdChzdHJzcGxpdChwb3ZlcnR5W2ksMl0sICJbKCldIikpWzJdCiAgcG92ZXJ0eVtpLDFdIDwtIHN0YXRlc0FiYnNbbWF0Y2goc3RhdGUsIHN0YXRlc0FiYnMkc3RhdGUuYWJiKSwyXQogIHBvdmVydHlbaSwyXSA8LSBnc3ViKCJDb3VudHkiLCAiIiwgcG92ZXJ0eVtpLDJdKQogIHBvdmVydHlbaSwyXSA8LSBnc3ViKCJDZW5zdXMgQXJlYSIsICIiLCBwb3ZlcnR5W2ksMl0pCiAgcG92ZXJ0eVtpLDJdIDwtIGdzdWIoIkJvcm91Z2giLCAiIiwgcG92ZXJ0eVtpLDJdKQogIGdldFJpZE9mUGFycyA8LSB1bmxpc3Qoc3Ryc3BsaXQocG92ZXJ0eVtpLDJdLCAiWyhdIikpWzFdCiAgcG92ZXJ0eVtpLDJdIDwtIHN0cl90cmltKGdldFJpZE9mUGFycykKfQoKIyBleHBvcnQgZGF0YSBiYWNrIHdpdGggdGhlIGZpeGVkIHJhY2UsIGdlbmRlciwgYW5kIHBvdmVydHkgZGF0YQp3cml0ZS5jc3YocmFjZSwgIlJhY2VGaXhlZC5jc3YiLCByb3cubmFtZXMgPSBGQUxTRSkKd3JpdGUuY3N2KGdlbmRlciwgIkdlbmRlckZpeGVkLmNzdiIsIHJvdy5uYW1lcyA9IEZBTFNFKQp3cml0ZS5jc3YocG92ZXJ0eSwgIlBvdmVydHlGaXhlZC5jc3YiLCByb3cubmFtZXMgPSBGQUxTRSkKYGBgCjxoMj4gRGF0YWJhc2UgU3RvcmFnZSA8L2gyPgo8cD5BIHJlbGF0aW9uYWwgU1FMIGRhdGFiYXNlIHdhcyB0aGVuIGNyZWF0ZWQgaW4gbXkgbXlTUUwgdG8gc3RvcmUgYWxsIHZhbHVlcyBmb3IgYWlyIHF1YWxpdHkgbWVhc3VyZW1lbnRzIGFuZCB0aGUgZGVtb2dyYXBoaWMgbWVhc3VyZW1lbnRzIGJ5IHN0YXRlLjwvcD4KPHA+VGhlIGRhdGFiYXNlIGhhcyB0YWJsZXMgZm9yIHBvdmVydHksIHJhY2UsIGdlbmRlciwgY2l0aXplbnNoaXAsIGFuZCBhaXIgcXVhbGl0eS48L3A+CmBgYHtyfQojIGluc3RhbGwgYW5kIGxvYWQgcGFja2FnZSB0byBjb25uZWN0IHRvIG15U1FMCiNpbnN0YWxsLnBhY2thZ2VzKCJSTXlTUUwiKQpsaWJyYXJ5KFJNeVNRTCkKCiMgZXN0YWJsaXNoIGEgY29ubmVjdGlvbiB3aXRoIHRoZSBsb2NhbCBlbnZpX21vZGVsIGRhdGFiYXNlCmRyaXZlciA8LSBkYkRyaXZlcigiTXlTUUwiKQpjb25uIDwtCiAgZGJDb25uZWN0KGRyaXZlciwKICAgICAgICAgICAgdXNlciA9ICJyb290IiwKICAgICAgICAgICAgcGFzcyA9ICIxMjM0IiwKICAgICAgICAgICAgZGJuYW1lID0gImVudmlfbW9kZWwiKQpgYGAKPHA+VGhlcmUgYXJlIG11bHRpcGxlIHZhbHVlcyBmb3IgZWFjaCBzdGF0ZSBpbiB0aGUgYWlyIHF1YWxpdHkgZGF0YS4gVGhlcmVmb3JlLCB0aGVzZSB2YWx1ZXMgd2VyZSBhZ2dyZWdhdGVkIGJ5IGF2ZXJhZ2luZyBpbiBteVNRTCBiZWZvcmUgam9pbmluZyBhbGwgb2YgdGhlIHRhYmxlcyB0byBjcmVhdGUgYSBtYXN0ZXIgdGFibGUuPC9wPgo8cD5JbiBvcmRlciB0byBjcmVhdGUgYSBwcmVkaWN0aXZlIG1vZGVsLCB0aGUgdGFibGVzIGluIHRoZSBkYXRhYmFzZSBhcmUgYWxsIHRoZW4gam9pbmVkIHRvIGNyZWF0ZSBhIG1hc3RlciB0YWJsZSBvZiBkZW1vZ3JhcGhpY3MgYW5kIGFpciBxdWFsaXR5IGJ5IHN0YXRlLiA8L3A+CmBgYHtyfQojIGFnZ3JlZ2F0ZSB0aGUgYWlyIHF1YWxpdHkgdGFibGUgYnkgc3RhdGUgYW5kIGF2ZXJhZ2UgYWxsIG9mIHRoZSB2YWx1ZXMgZm9yIGVhY2ggb2YgdGhlIGNvbHVtbnMKIyBqb2luIGFsbCBvZiBleGlzdGluZyBtZWFzdXJpbmcgdGFibGVzIGJ5IHN0YXRlCnF1ZXJ5IDwtCiAgZGJTZW5kUXVlcnkoCiAgICBjb25uLAogICAgc3RhdGVtZW50ID0gIlNFTEVDVCBESVNUSU5DVAoJYWlyX3F1YWxpdHkuc3RhdGUsCiAgICBhaXJfcXVhbGl0eS5jb3VudHksCiAgICBwb3ZlcnR5LnBvdmVydHlfcGVyY2VudCwKICAgIGdlbmRlci5tYWxlX3BlcmNlbnQsCiAgICByYWNlLndoaXRlX3BlcmNlbnQsCiAgICByYWNlLmJsYWNrX3BlcmNlbnQsCiAgICByYWNlLmhpc3BhbmljX3BlcmNlbnQsCiAgICByYWNlLmFzaWFuX3BlcmNlbnQsCiAgICByYWNlLm5hdGl2ZV9wZXJjZW50LAogICAgYWlyX3F1YWxpdHkuZ29vZF9kYXlzLAogICAgYWlyX3F1YWxpdHkubW9kZXJhdGVfZGF5cywKICAgIGFpcl9xdWFsaXR5LnVuaGVhbHRoeV9kYXlzLAogICAgYWlyX3F1YWxpdHkudmVyeV91bmhlYWx0aHlfZGF5cywKICAgIGFpcl9xdWFsaXR5LmhhemFyZG91c19kYXlzCkZST00KICAgIChTRUxFQ1QgRElTVElOQ1QgKiBGUk9NIGdlbmRlcikgQVMgZ2VuZGVyLAogICAgKFNFTEVDVCBESVNUSU5DVCAqIEZST00gcmFjZSkgQVMgcmFjZSwKICAgIChTRUxFQ1QgRElTVElOQ1QgKiBGUk9NIHBvdmVydHkpIEFTIHBvdmVydHksCiAgICAoU0VMRUNUIERJU1RJTkNUICogRlJPTSBhaXJfcXVhbGl0eSkgQVMgYWlyX3F1YWxpdHkKV0hFUkUKCWdlbmRlci5zdGF0ZSA9IGdlbmRlci5zdGF0ZSBBTkQKICAgIGdlbmRlci5zdGF0ZSA9IHJhY2Uuc3RhdGUKICAgICAgICBBTkQgZ2VuZGVyLmNvdW50eSA9IHJhY2UuY291bnR5CiAgICAgICAgQU5EIHJhY2Uuc3RhdGUgPSBwb3ZlcnR5LnN0YXRlCgkJQU5EIHJhY2UuY291bnR5ID0gcG92ZXJ0eS5jb3VudHkKICAgICAgICBBTkQgcG92ZXJ0eS5zdGF0ZSA9IGFpcl9xdWFsaXR5LnN0YXRlCiAgICAgICAgQU5EIHBvdmVydHkuY291bnR5ID0gYWlyX3F1YWxpdHkuY291bnR5Ck9SREVSIEJZIGFpcl9xdWFsaXR5LnN0YXRlIgogICkKYWxsRGF0YSA8LSBmZXRjaChxdWVyeSwgbj0tMSkKd3JpdGUuY3N2KGFsbERhdGEsICJBbGxEYXRhLmNzdiIsIHJvdy5uYW1lcyA9IEZBTFNFKQpgYGAKPGgyPiBEYXRhIFZpc3VhbGl6YXRpb25zIDwvaDI+CjxwPkJlZm9yZSBjcmVhdGluZyBhIG1vZGVsIGZvciBhbmQgZXZhbHVhdGlvbiB0aGUgZGF0YSwgSSB1c2VkIFRhYmxlYXUgdG8gY3JlYXRlIHNldmVyYWwgdmlzdWFsaXphdGlvbnMgdGhhdCBkZW1vbnN0cmF0ZSBzb21lIG9mIHRoZSBmZWF0dXJlcyBieSBzdGF0ZSBhbmQgY291bnR5IGFzIHRoZSBkYXRhIGlzIGludGVyZXN0aW5nIHRvIHZpc3VhbGl6ZS48L3A+Cgo8aW1nIHNyYyA9ICIvVXNlcnMvdmlrYWJhL0RvY3VtZW50cy9Eb2N1bWVudHMvbm9ydGhlYXN0ZXJuL3NlY29uZF95ZWFyL0RTNDEwMC9maW5hbF9wcm9qZWN0L1BvdmVydHkgQnkgU3RhdGUucG5nIj4KPGltZyBzcmMgPSAiL1VzZXJzL3Zpa2FiYS9Eb2N1bWVudHMvRG9jdW1lbnRzL25vcnRoZWFzdGVybi9zZWNvbmRfeWVhci9EUzQxMDAvZmluYWxfcHJvamVjdC9XaGl0ZSBCeSBTdGF0ZS5wbmciPgo8aW1nIHNyYyA9ICIvVXNlcnMvdmlrYWJhL0RvY3VtZW50cy9Eb2N1bWVudHMvbm9ydGhlYXN0ZXJuL3NlY29uZF95ZWFyL0RTNDEwMC9maW5hbF9wcm9qZWN0L1BvdmVydHkgdnMgUGVyY2VudCBvZiBXaGl0ZSBQZW9wbGUgcGVyIENvdW50eS5wbmciPgo8cD4gQXMgY2FuIGJlIHNlZW4gZnJvbSB0aGlzIHZpc3VhbGl6YXRpb24sIHRoZSBzbWFsbGVyIGNpcmNsZXMgd2hpY2ggcmVwcmVzZW50IGEgbG93IHBlcmNlbnRhZ2Ugb2Ygd2hpdGUgcGVvcGxlLCBhcmUgbW9zdGx5IGRhcmtlciBjb2xvcmVkIHRoYW4gdGhlIGJpZ2dlciBjaXJjbGVzLCBtZWFuaW5nIHRoZXkgaGF2ZSBhIGhpZ2hlciBwb3ZlcnR5IHBlcmNlbnRhZ2UuIDwvcD4KPGltZyBzcmMgPSAiL1VzZXJzL3Zpa2FiYS9Eb2N1bWVudHMvRG9jdW1lbnRzL25vcnRoZWFzdGVybi9zZWNvbmRfeWVhci9EUzQxMDAvZmluYWxfcHJvamVjdC9Hb29kIERheXMgQnkgU3RhdGUucG5nIj4KCjxoMj4gRXZhbHVhdGluZyB0aGUgRGF0YSA8L2gyPgoKPGg0PiBPdXRsaWVycyA8L2g0Pgo8cD5GaXJzdCwgb3V0bGllcnMgd2VyZSBpZGVudGlmaWVkIGluIHRoZSByZXNwb25zZSB2YXJpYWJsZSBvZiBnb29kIGRheXMgYnkgc2VlaW5nIHdoaWNoIHZhbHVlcyB3ZXJlIGFib3V0IDMgc3RhbmRhcmQgZGV2aWF0aW9ucyBmcm9tIHRoZSBtZWFuLiA8L3A+CjxwPiBCZWNhdXNlIHRoaXMgbW9kZWwgZGVhbHMgd2l0aCBhaXIgcXVhbGl0eSBkYXRhLCBJIGRpZCBub3QgdGhpbmsgaXQgd2FzIG5lY2Vzc2FyeSB0byBvbWl0IHRoZXNlIG91dGxpZXJzLiBGcm9tIGxvb2tpbmcgYXQgdGhlIG91dGxpZXJzIChvZiB3aGljaCB0aGVyZSB3ZXJlIG9ubHkgOCksIHRoZSBudW1iZXIgb2YgZ29vZCBkYXlzIHdhcyBsb3cgZm9yIHRob3NlIGRheXMgYmVjYXVzZSBtb2RlcmF0ZSBvciB1bmhlYWx0aHkgZGF5cyB3ZXJlIGhpZ2ggYW5kIEkgdGhpbmsgdGhhdCBpcyBpbXBvcnRhbnQgZGF0YSB0byBoYXZlLiBJdCBhbHNvIGRpZCBub3Qgc2VlbSBsaWtlIHRoZXNlIG91dGxpZXJzIHdlcmUgZHVlIHRvIHdyb25nZnVsIGV4cGVyaW1lbnRhdGlvbi4gPC9wPgpgYGB7cn0KZ29vZERheXNNZWFuIDwtIG1lYW4oYWxsRGF0YSRnb29kX2RheXMpICMgZ29vZCBkYXlzIG1lYW4KZ29vZERheXNTdERldiA8LSBzZChhbGxEYXRhJGdvb2RfZGF5cykgIyBnb29kIGRheXMgc3QgZGV2CmFsbERhdGEkc3REZXZGcm9tTWVhbiA8LSAoKGFsbERhdGEkZ29vZF9kYXlzIC0gZ29vZERheXNNZWFuKSAvIGdvb2REYXlzU3REZXYpCgojIGZpbmQgYW55IHZhbHVlcyBpbiBnb29kIGRheXMgdGhhdCBhcmUgMyBvciBtb3JlIHN0YW5kYXJkIGRldmlhdGlvbnMgZnJvbSB0aGUgbWVhbgpvdXRsaWVycyA8LSBhbGxEYXRhW3doaWNoKGFicyhhbGxEYXRhJHN0RGV2RnJvbU1lYW4pID49IDMpLF0Kb3V0bGllcnMKYGBgCjxoND4gRGlzdHJpYnV0aW9uIDwvaDQ+CjxwPlRoZSByZXNwb25zZSB2YXJpYWJsZSBvZiBnb29kIGRheXMgd2FzIHRoZW4gYW5hbHl6ZWQgZm9yIG5vcm1hbCBkaXN0cmlidXRpb24gd2l0aCBhIGhpc3RvZ3JhbS48L3A+CmBgYHtyfQpoaXN0KGFsbERhdGEkZ29vZF9kYXlzKQpgYGAKPHA+QSBzcXVhcmVkIHRyYW5zZm9ybSB3YXMgdGhlbiBhcHBsaWVkIHRvIHRoZSBnb29kIGRheXMgdG8gbm9ybWFsaXplIHRoZSBnb29kIGRheXMgdmFyaWFibGUuPC9wPgpgYGB7cn0KIyBzY2F0dGVyIHBsb3Qgb2YgYWlyIHF1YWxpdHkgZ29vZCBkYXlzIGRhdGEKaGlzdCgoYWxsRGF0YSRnb29kX2RheXMpXjIpCgojIHRyYW5zZm9ybSB0aGUgYXZlcmFnZSBnb29kIGRheXMgYnkgc3F1YXJlIHJvb3QKYWxsRGF0YUdvb2REYXlzTm9ybWFsaXplZCA8LSBhbGxEYXRhWyxjKC0xLC0yLC0xMTotMTUpXQphbGxEYXRhR29vZERheXNOb3JtYWxpemVkJGdvb2RfZGF5cyA8LSAoKGFsbERhdGEkZ29vZF9kYXlzKV4yKQpgYGAKPGg0PiBBTk9WQSA8L2g0Pgo8cD5BTk9WQSdzIHdlcmUgZG9uZSB0byBzZWUgaWYgY2VydGFpbiBmZWF0dXJlcyBhbG9uZSB3ZXJlIGVub3VnaCB0byBwcmVkaWN0IHRoZSBwZXJjZW50YWdlIG9mIGdvb2QgZGF5cyBmb3IgYSBsb2NhdGlvbi4gVGhlc2Ugd2VyZSBkb25lIGZvciBtYWxlIHBlcmNlbnRhZ2UsIHdoaXRlIHBlcmNlbnRhZ2UsIGFuZCBwb3ZlcnR5IHBlcmNlbnRhZ2UuPC9wPgo8cD5UaGVuLCBBTk9WQSdzIHdlcmUgZG9uZSB0byBzZWUgaWYgdGhlc2UgZmVhdHVyZXMgd2l0aCBvdGhlciBmZWF0dXJlcyBwcmVzZW50IHdlcmUgYSBzaWduaWZpY2FudCBwcmVkaWN0b3IgdmFyaWFibGUuPC9wPgoKPGg1Pk1hbGUgUGVyY2VudGFnZSBPbmx5PC9oNT4KYGBge3J9CiMgYW5vdmEgdG8gc2VlIGlmIG1hbGUgcGVyY2VudGFnZSBhbG9uZSBpcyBlbm91Z2ggdG8gcHJlZGljdCBudW1iZXIgb2YgZ29vZCBkYXlzCmFvdi5nZW5kZXJPbmx5IDwtIGFvdihmb3JtdWxhID0gZ29vZF9kYXlzIH4gbWFsZV9wZXJjZW50LCBkYXRhID0gYWxsRGF0YUdvb2REYXlzTm9ybWFsaXplZCkKc3VtbWFyeShhb3YuZ2VuZGVyT25seSkKYGBgCjxwPiBCZWNhdXNlIHRoZSBwIHZhbHVlIGlzIGxlc3MgdGhhbiAwLjA1LCBtYWxlIHBlcmNlbnRhZ2UgYWxvbmUgaXMgZW5vdWdoIHRvIHByZWRpY3QgbnVtYmVyIG9mIGdvb2QgZGF5cy4gPC9wPgoKPGg1PldoaXRlIFBlcmNlbnRhZ2UgT25seTwvaDU+CmBgYHtyfQojIGFub3ZhIHRvIHNlZSBpZiB3aGl0ZSBwZXJjZW50YWdlIGFsb25lIGlzIGVub3VnaCB0byBwcmVkaWN0IG51bWJlciBvZiBnb29kIGRheXMKYW92LndoaXRlT25seSA8LSBhb3YoZm9ybXVsYSA9IGdvb2RfZGF5cyB+IHdoaXRlX3BlcmNlbnQsIGRhdGEgPSBhbGxEYXRhR29vZERheXNOb3JtYWxpemVkKQpzdW1tYXJ5KGFvdi53aGl0ZU9ubHkpCmBgYAo8cD4gQmVjYXVzZSB0aGUgcCB2YWx1ZSBpcyBsZXNzIHRoYW4gMC4wNSwgd2hpdGUgcGVyY2VudGFnZSBhbG9uZSBpcyBlbm91Z2ggdG8gcHJlZGljdCBudW1iZXIgb2YgZ29vZCBkYXlzLiA8L3A+Cgo8aDU+UG92ZXJ0eSBQZXJjZW50YWdlIE9ubHk8L2g1PgpgYGB7cn0KIyBhbm92YSB0byBzZWUgaWYgcG92ZXJ0eSBwZXJjZW50YWdlIGFsb25lIGlzIGVub3VnaCB0byBwcmVkaWN0IG51bWJlciBvZiBnb29kIGRheXMKYW92LnBvdmVydHlPbmx5IDwtIGFvdihmb3JtdWxhID0gZ29vZF9kYXlzIH4gcG92ZXJ0eV9wZXJjZW50LCBkYXRhID0gYWxsRGF0YUdvb2REYXlzTm9ybWFsaXplZCkKc3VtbWFyeShhb3YucG92ZXJ0eU9ubHkpCmBgYAo8cD4gQmVjYXVzZSB0aGUgcCB2YWx1ZSBpcyBncmVhdGVyIHRoYW4gMC4wNSwgcG92ZXJ0eSBwZXJjZW50YWdlIGFsb25lIGlzIG5vdCBlbm91Z2ggdG8gcHJlZGljdCBudW1iZXIgb2YgZ29vZCBkYXlzLiA8L3A+Cgo8aDU+QWxsIFBlcmNlbnRhZ2VzIHRvIFNlZSBpZiBQb3ZlcnR5LCBNYWxlLCBhbmQgV2hpdGUgUGVyY2VudGFnZXMgU2lnbmlmaWNhbnQgUHJlZGljdG9yczwvaDU+CmBgYHtyfQojIGFub3ZhIHRvIHNlZSBpZiBwb3ZlcnR5LCB3aGl0ZSwgYW5kIG1hbGUgcGVyY2VudGFnZXMgd2l0aCBvdGhlciBmZWF0dXJlcyBhcmUgc2lnbmlmaWNhbnQgcHJlZGljdG9ycwphb3YuZ29vZERheXNBbGwgPC0gYW92KGZvcm11bGEgPSBnb29kX2RheXMgfiAuLCBkYXRhID0gYWxsRGF0YUdvb2REYXlzTm9ybWFsaXplZCkKc3VtbWFyeShhb3YuZ29vZERheXNBbGwpCmBgYAo8cD4gVGhlIHAgdmFsdWVzIGZvciBtYWxlIGFuZCB3aGl0ZSBwZXJjZW50YWdlcyBhcmUgbGVzcyB0aGFuIDAuMDUsIG1ha2luZyB0aGVtIHNpZ25pZmljYW50IHByZWRpY3RvcnMuIFRoZSBwIHZhbHVlIGZvciBwb3ZlcnR5IHBlcmNlbnRhZ2UgaXMgZ3JlYXRlciB0aGFuIDAuMDUsIHRoZXJlZm9yZSBpdCBpcyBub3QgYSBzaWduaWZpY2FudCBwcmVkaWN0b3Igd2hlbiBhbGwgb2YgdGhlIG90aGVyIGZlYXR1cmVzIHByZXNlbnQgPC9wPgoKPGg0PiBTY2F0dGVyIFBsb3RzIDwvaDQ+CjxwPiBTY2F0dGVyIHBsb3RzIHdlcmUgYWxzbyBkb25lIHRvIHNlZSBpZiB0aGVyZSB3YXMgYSBsaW5lYXIgcmVncmVzc2lvbiByZWxhdGlvbnNoaXAgYmV0d2VlbiBnZW5kZXIsIHJhY2UsIGFuZCBwb3ZlcnR5IGZlYXR1cmVzIGFuZCBhaXIgcXVhbGl0eSBudW1iZXJzIDwvcD4KCjxoNT4gTWFsZSBQZXJjZW50YWdlIHZzIEdvb2QgRGF5cyA8L2g1PgpgYGB7cn0Kc2NhdHRlci5zbW9vdGgoYWxsRGF0YUdvb2REYXlzTm9ybWFsaXplZCRtYWxlX3BlcmNlbnQsIGFsbERhdGFHb29kRGF5c05vcm1hbGl6ZWQkZ29vZF9kYXlzKQpgYGAKPHA+IFRoZXJlIGRvZXMgbm90IHNlZW0gdG8gYmUgYSBzdHJvbmcgY29ycmVsYXRpb24gYmV0d2VlbiBnb29kIGRheXMgYW5kIHBlcmNlbnQgb2YgbWFsZXMuIEdvb2QgZGF5cyBzZWVtIHRvIGJlIGNvbnN0YW50IHdoZW4gY29tcGFyZWQgdG8gcGVyY2VudGFnZSBvZiBtYWxlcy48L3A+Cgo8aDU+IFdoaXRlIFBlcmNlbnRhZ2UgdnMgR29vZCBEYXlzIDwvaDU+CmBgYHtyfQpzY2F0dGVyLnNtb290aChhbGxEYXRhR29vZERheXNOb3JtYWxpemVkJHdoaXRlX3BlcmNlbnQsIGFsbERhdGFHb29kRGF5c05vcm1hbGl6ZWQkZ29vZF9kYXlzKQpgYGAKPHA+IEluIHRoaXMgcGxvdCwgdGhlIGRhdGEgc2VlbXMgc2NhdHRlcmVkIHdpdGhvdXQgYSBzaWduaWZpY2FudCBjb3JyZWxhdGlvbiBiZXR3ZWVuIHdoaXRlIHBlcmNlbnRhZ2UgYW5kIGdvb2QgZGF5cy48L3A+Cgo8aDU+IE1hbGUgUGVyY2VudGFnZSB2cyBHb29kIERheXMgPC9oNT4KYGBge3J9CnNjYXR0ZXIuc21vb3RoKGFsbERhdGFHb29kRGF5c05vcm1hbGl6ZWQkcG92ZXJ0eV9wZXJjZW50LCBhbGxEYXRhR29vZERheXNOb3JtYWxpemVkJGdvb2RfZGF5cykKYGBgCjxwPiBUaGlzIHBsb3QgYWxzbyBzaG93cyBubyBzaWduaWZpY2FudCBjb3JyZWxhdGlvbiBhcyB0aGUgcG9pbnRzIGFyZSB2ZXJ5IHNjYXR0ZXJlZC4gPC9wPgoKPGg0PiBTcGVhcm1hbiBSYW5rIGFuZCBQZWFyc29uIE1vbWVudCBDb2VmZmljaWVudHMgPC9oND4KPHA+IFNwZWFybWFuIGFuZCBQZWFyc29uIGNvZWZmaWNpZW50cyB3ZXJlIGFsc28gdXNlZCB0byBhc3Nlc3MgY29ycmVsYXRpb24gYmV0d2VlbiBwb3ZlcnR5LCByYWNlLCBhbmQgZ2VuZGVyIHBlcmNlbnRhZ2VzIDwvcD4KCjxwPiBCZWZvcmUgdGhlIHRlc3RzIHdlcmUgcGVyZm9ybWVkIG9uIHRoZXNlIGZlYXR1cmVzLCBhbiBlZmZvcnQgd2FzIG1hZGUgdG8gbm9ybWFsaXplIHRoZW0gZm9yIGJldHRlciBjb3JyZWxhdGlvbiBjb2VmZmljaWVudCB0ZXN0aW5nLiBObyB0cmFuc2Zvcm1zIHdlcmUgbWFkZSBvbiBhbnkgb2YgdGhlIGRhdGEuIFRoZSBwb3ZlcnR5IGRhdGEgd2FzIHJlbGF0aXZlbHkgbm9ybWFsbHkgZGlzdHJpYnV0ZWQuIFRoZSByYWNlIGFuZCBnZW5kZXIgZGF0YSB3YXMgc2tld2VkIGJ1dCBkaWQgbm90IHJlc3BvbmQgd2VsbCB0byB0cmFuc2Zvcm1zLiA8L3A+Cgo8aDU+IFBlYXJzb24gTW9tZW50IENvZWZmaWNpZW50OiBwZXJjZW50IG1hbGUgYW5kIG51bWJlciBvZiBnb29kIGFpciBxdWFsaXR5IGRheXM8L2g1PgpgYGB7cn0KY29yKGFsbERhdGFHb29kRGF5c05vcm1hbGl6ZWQkZ29vZF9kYXlzLCBhbGxEYXRhR29vZERheXNOb3JtYWxpemVkJG1hbGVfcGVyY2VudCwgbWV0aG9kID0gInBlYXJzb24iKQpgYGAKPGg1PiBTcGVhcm1hbiBSYW5rIENvZWZmaWNpZW50OiBwZXJjZW50IG1hbGUgYW5kIG51bWJlciBvZiBnb29kIGFpciBxdWFsaXR5IGRheXMgPC9oNT4KYGBge3J9CmNvcihhbGxEYXRhR29vZERheXNOb3JtYWxpemVkJGdvb2RfZGF5cywgYWxsRGF0YUdvb2REYXlzTm9ybWFsaXplZCRtYWxlX3BlcmNlbnQsIG1ldGhvZCA9ICJzcGVhcm1hbiIpCmBgYAo8aDU+IFBlYXJzb24gTW9tZW50IENvZWZmaWNpZW50OiBwZXJjZW50IHdoaXRlIGFuZCBudW1iZXIgb2YgZ29vZCBhaXIgcXVhbGl0eSBkYXlzIDwvaDU+CmBgYHtyfQpjb3IoYWxsRGF0YUdvb2REYXlzTm9ybWFsaXplZCRnb29kX2RheXMsIGFsbERhdGFHb29kRGF5c05vcm1hbGl6ZWQkd2hpdGVfcGVyY2VudCwgbWV0aG9kID0gInBlYXJzb24iKQpgYGAKPGg1PiBTcGVhcm1hbiBSYW5rIENvZWZmaWNpZW50OiBwZXJjZW50IHdoaXRlIGFuZCBudW1iZXIgb2YgZ29vZCBhaXIgcXVhbGl0eSBkYXlzIDwvaDU+CmBgYHtyfQpjb3IoYWxsRGF0YUdvb2REYXlzTm9ybWFsaXplZCRnb29kX2RheXMsIGFsbERhdGFHb29kRGF5c05vcm1hbGl6ZWQkd2hpdGVfcGVyY2VudCwgbWV0aG9kID0gInNwZWFybWFuIikKYGBgCjxoNT4gUGVhcnNvbiBNb21lbnQgQ29lZmZpY2llbnQ6IHBlcmNlbnQgcG92ZXJ0eSBhbmQgbnVtYmVyIG9mIGdvb2QgYWlyIHF1YWxpdHkgZGF5cyA8L2g1PgpgYGB7cn0KY29yKGFsbERhdGFHb29kRGF5c05vcm1hbGl6ZWQkZ29vZF9kYXlzLCBhbGxEYXRhR29vZERheXNOb3JtYWxpemVkJHBvdmVydHlfcGVyY2VudCwgbWV0aG9kID0gInBlYXJzb24iKQpgYGAKPGg1PiBTcGVhcm1hbiBSYW5rIENvZWZmaWNpZW50OiBwZXJjZW50IHBvdmVydHkgYW5kIG51bWJlciBvZiBnb29kIGFpciBxdWFsaXR5IGRheXM8L2g1CmBgYHtyfQpjb3IoYWxsRGF0YUdvb2REYXlzTm9ybWFsaXplZCRnb29kX2RheXMsIGFsbERhdGFHb29kRGF5c05vcm1hbGl6ZWQkcG92ZXJ0eV9wZXJjZW50LCBtZXRob2QgPSAic3BlYXJtYW4iKQpgYGAKPHA+IEZvciBhbGwgb2YgdGhlIGZlYXR1cmVzIGV2YWx1YXRlZCwgdGhlIFNwZWFybWFuIFJhbmsgYW5kIFBlYXJzb24gTW9tZW50IGNvZWZmaWNpZW50cyBhcmUgcmVsYXRpdmVseSBjbG9zZSB0byBlYWNoIG90aGVyLiBUaGlzIG1lYW5zIHRoYXQgb3V0bGllcnMgZG8gbm90IHNlZW0gdG8gYmUgaGF2aW5nIGFuIGVmZmVjdCBvbiB0aGUgZGF0YS4gSG93ZXZlciwgYWxsIG9mIHRoZSBjb2VmZmljaWVudHMgYXJlIHZlcnkgbG93IGFuZCBzaWduaWZpY2FudGx5IGJlbG93IDAuOC4gQmVjYXVzZSBub25lIG9mIHRoZSBjb2VmZmljaWVudHMgYXJlIDAsIHRoZXJlIHNlZW1zIHRvIGJlIHNvbWUgY29ycmVsYXRpb24gYmV0d2VlbiB0aGVzZSBmZWF0dXJlcyBhbmQgdGhlIG51bWJlciBvZiBnb29kIGFpciBxdWFsaXR5IGRheXMsIGJ1dCBub3QgZW5vdWdoIHRvIHNheSB0aGF0IHRoZXJlIGlzIHNpZ25pZmljYW50IGNvcnJlbGF0aW9uLgpUaGVyZSBpcyB0aGUgbGVhc3QgY29ycmVsYXRpb24gYmV0d2VlbiBwZXJjZW50IHBvdmVydHkgYW5kIGdvb2QgYWlyIHF1YWxpdHkgYW5kIHRoZSBtb3N0IGNvcnJlbGF0aW9uIGJldHdlZW4gcGVyY2VudCBvZiBtYWxlcyBhbmQgZ29vZCBhaXIgcXVhbGl0eS4gTm9uZSBvZiB0aGVzZSBjb3JyZWxhdGlvbnMsIGhvd2V2ZXIsIGFyZSBzdHJvbmcuPC9wPgoKPHA+IFRoZSByYWNlIGFuZCBnZW5kZXIgZGF0YSBpcyBhbHNvIG5vdCBub3JtYWxseSBkaXN0cmlidXRlZCB3aGljaCBtYXkgYmUgYWZmZWN0aW5nIHRoZSBjb3JyZWxhdGlvbiBjb2VmZmljaWVudHMuIEhvd2V2ZXIsIGJlY2F1c2UgdGhlIFNwZWFybWFuIFJhbmsgYW5kIFBlYXJzb24gTW9tZW50IGNvZWZmaWNpZW50cyBmb3IgdGhlc2UgMiBmZWF0dXJlcyB3ZXJlIHJlbGF0aXZlbHkgY2xvc2UsIHRoZSBkaXN0cmlidXRpb24gZG9lcyBub3Qgc2VlbSB0byBiZSBzaWduaWZpY2FudGx5IGFmZmVjdGluZyB0aGUgY29ycmVsYXRpb24gY29lZmZpY2llbnRzIDwvcD4KCjxoMj4gQnVpbGRpbmcgdGhlIE1vZGVsKHMpIDwvaDI+CjxwPlRoZSBkZXNpcmVkIG1vZGVsIGZvciB0aGlzIGRhdGFzZXQgaXMgYSBtdWx0aXBsZSBsaW5lYXIgcmVncmVzc2lvbiBtb2RlbCB3aXRoIHNvbWUgcmVzcG9uc2UgdmFyaWFibGUgZGVhbGluZyB3aXRoIHRoZSBudW1iZXIgb2YgZGF5cyBvZiBhIGNlcnRhaW4gYWlyIHF1YWxpdHkuCgo8aDQ+IE1vZGVsIHdpdGggR29vZCBEYXlzIGFzIFJlc3BvbnNlIFZhcmlhYmxlIDwvaDQ+CjxwPiBUaGUgZmlyc3QgbW9kZWwgaGFzIHRoZSBnb29kIGRheXMgYXMgYSByZXNwb25zZSB2YXJpYWJsZS48L3A+CjxwPiBUaGUgZGF0YSB3YXMgZmlyc3QgcmFuZG9tbHkgc3BsaXQgdXAgNTAtNTAgaW50byBhIHRyYWluaW5nIGFuZCB2YWxpZGF0aW9uIHNldHMuPC9wPgpgYGB7cn0KIyB0cmFpbmluZyBkYXRhIHNldDogcmFuZG9tIGhhbGYgb2YgdGhlIGRhdGEKdHJhaW5pbmcgPC0gYWxsRGF0YUdvb2REYXlzTm9ybWFsaXplZFtzYW1wbGUobnJvdyhhbGxEYXRhR29vZERheXNOb3JtYWxpemVkKSwgbnJvdyhhbGxEYXRhR29vZERheXNOb3JtYWxpemVkKSAvIDIpLF0KCiMgdGVzdCBkYXRhIHNldDogdGhlIG90aGVyIGhhbGYgb2YgdGhlIGRhdGEKdGVzdCA8LSBhbGxEYXRhR29vZERheXNOb3JtYWxpemVkWy1jKGFzLm51bWVyaWMocm93bmFtZXModHJhaW5pbmcpKSksXQpgYGAKPHA+IFRvIGNyZWF0ZSBhIG11bHRpcGxlIGxpbmVhciByZWdyZXNzaW9uIG1vZGVsLCBiYWNrd2FyZCBmaXR0aW5nIHdhcyB1c2VkIGFuZCBmZWF0dXJlcyB3aXRoIHAgdmFsdWVzID4gMC4wNSB3ZXJlIHJlbW92ZWQgYXQgZWFjaCBzdGVwLiA8L3A+CmBgYHtyfQojIGxpbmVhciByZWdyZXNzaW9uIG1vZGVsIHVzaW5nIHRyYWluaW5nIGRhdGEgYW5kIGJhY2t3YXJkIGZpdHRpbmcsIG9ubHkga2VlcGluZyBwIHZhbHVlcyA8IDAuMDUKZ29vZERheXNNb2RlbCA8LSBsbShmb3JtdWxhID0gZ29vZF9kYXlzIH4gLi0oaGlzcGFuaWNfcGVyY2VudCtwb3ZlcnR5X3BlcmNlbnQrYXNpYW5fcGVyY2VudCtibGFja19wZXJjZW50KSwgZGF0YSA9IHRyYWluaW5nKQpzdW1tYXJ5KGdvb2REYXlzTW9kZWwpCmBgYAo8cD4gTmV4dCwgdXNpbmcgdGhlIHRlc3QgZGF0YSBzZXQgd2FzIHVzZWQgdG8ganVkZ2UgdGhlIGFjY3VyYWN5IG9mIHRoZSBtb2RlbCBieSBjYWxjdWxhdGluZyB0aGUgcHJlZGljdGlvbiBhY2N1cmFjeSB3aXRoIHRoZSBwcmVkaWN0ZWQgdmFsdWVzIGZvciB0aGUgdGVzdCBkYXRhIHNldC4gQUlDIGFuZCBCSUMgY2FsY3VsYXRpb25zIHdlcmUgYWxzbyBwZXJmb3JtZWQgYXMgdGhleSBhcmUgZ29vZCBtZWFzdXJlcyBmb3IgbW9kZWwgc2VsZWN0aW9uLCBpbiBjYXNlIG90aGVyIG1vZGVscyBhcmUgY3JlYXRlZCBmb3IgdGhpcyBkYXRhLiA8L3A+CmBgYHtyfQojIHJldmVyc2UgdGhlIHRyYW5zZm9ybSBvZiB0aGUgZGF0YSBhbmQgbWFrZSBwcmVkaWN0aW9ucyBiYXNlZCBvbiB0aGF0IGRhdGEKdGVzdFVubm9ybWFsaXplZCA8LSB0ZXN0CnRlc3RVbm5vcm1hbGl6ZWQkZ29vZF9kYXlzIDwtIGFsbERhdGFbLWMoYXMubnVtZXJpYyhyb3duYW1lcyh0cmFpbmluZykpKSxjKCJnb29kX2RheXMiKV0KZ29vZERheXNQcmVkcyA8LSBwcmVkaWN0KGdvb2REYXlzTW9kZWwsIHRlc3RVbm5vcm1hbGl6ZWQpCgojIGRhdGEgZnJhbWUgb2YgYWN0dWFsIHZhbHVlcyB2cyBwcmVkaWN0ZWQgdmFsdWVzCmFjdHVhbHNfcHJlZHNfZ29vZCA8LSBjYmluZChkYXRhLmZyYW1lKGFjdHVhbHMgPSB0ZXN0VW5ub3JtYWxpemVkJGdvb2RfZGF5cywgcHJlZGljdGVkcyA9IGdvb2REYXlzUHJlZHMpKQoKIyBwcmVkaWN0aW9uIGFjY3VyYWN5CmNvcnJlbGF0aW9uX2FjY3VyYWN5IDwtIGNvcihhY3R1YWxzX3ByZWRzX2dvb2QpCnBhc3RlKCJQcmVkaWN0aW9uIEFjY3VyYWN5OiAiLCBjb3JyZWxhdGlvbl9hY2N1cmFjeVsxLDJdICogMTAwLCAiJSIsIHNlcD0iIikKCiMgZml0IEFJQyBhbmQgQklDIGNhbGN1bGF0aW9ucwpwYXN0ZSgiQUlDOiIsIEFJQyhnb29kRGF5c01vZGVsKSkKcGFzdGUoIkJJQzoiLCBCSUMoZ29vZERheXNNb2RlbCkpCmBgYApVc2luZyB0aGUgdHJhaW5pbmcgYW5kIHRlc3QgZGF0YSBzZXQgdG8gY2FsY3VsYXRlIHRoZSBwcmVkaWN0aW9uIGFjY3VyYWN5LCB0aGUgbW9kZWwgd2FzIGNvcnJlY3QgYWJvdXQgMzklIG9mIHRoZSB0aW1lLgoKPGg0PiBNb2RlbCB3aXRoIENvbWJvIG9mIEdvb2QgYW5kIE1vZGVyYXRlIERheXMgYXMgUmVzcG9uc2UgVmFyaWFibGUgPC9oND4KPHA+IFRvIGNyZWF0ZSB0aGlzIG1vZGVsLCB0aGUgc3VtIG9mIGdvb2QgYW5kIG1vZGVyYXRlIGRheXMgd2FzIHRha2VuIHRvIGNyZWF0ZSB0aGUgZmVhdHVyZSBvZiBhY2NlcHRhYmxlIGFpciBxdWFsaXR5IGRheXMuIDwvcD4KYGBge3J9CmFsbERhdGFTdW1Hb29kTW9kZXJhdGUgPC0gYWxsRGF0YVssLTE1XQoKIyBjcmVhdGUgYWNjZXB0YWJsZSBkYXlzIGZlYXR1cmUKYWxsRGF0YVN1bUdvb2RNb2RlcmF0ZSRhY2NlcHRhYmxlX2RheXMgPC0gYWxsRGF0YSRnb29kX2RheXMgKyBhbGxEYXRhJG1vZGVyYXRlX2RheXMKCiMgb25seSBsZWF2ZSBhY2NlcHRhYmxlIGRheXMgZmVhdHVyZSBpbiBhbmQgb21pdCBvdXQgdGhlIHJlc3Qgb2YgdGhlIGFpciBxdWFsaXR5IGFuZCBzdGF0ZSBhbmQgY291bnR5IGZlYXR1cmVzCmFsbERhdGFTdW1Hb29kTW9kZXJhdGUgPC0gYWxsRGF0YVN1bUdvb2RNb2RlcmF0ZVssYygtMSwtMiwtMTA6LTE0KV0KYGBgCjxwPiBUaGVuLCBvdXRsaWVycyB3ZXJlIGlkZW50aWZpZWQgYWdhaW4gaW4gdGhlIHNhbWUgbWV0aG9kIG9mIHN0YW5kYXJkIGRldmlhdGlvbiBmcm9tIHRoZSBtZWFuIGFzIHRoZSBsYXN0IG1vZGVsLiBPbmNlIGFnYWluLCBJIGRlY2lkZWQgdG8gbGVhdmUgdGhlc2Ugb3V0bGllcnMgaW4gZm9yIG1vcmUgYWNjdXJhdGUgZGF0YSBhbmQgYmVjYXVzZSBJIGJlbGlldmUgdGhpcyBkYXRhIGlzIHNpZ25pZmljYW50IGZvciBhbmFseXNpcyA8L3A+CmBgYHtyfQphY2NlcHRhYmxlRGF5c01lYW4gPC0gbWVhbihhbGxEYXRhU3VtR29vZE1vZGVyYXRlJGFjY2VwdGFibGVfZGF5cykgIyBhY2NlcHRhYmxlIGRheXMgbWVhbgphY2NlcHRhYmxlRGF5c1N0RGV2IDwtIHNkKGFsbERhdGFTdW1Hb29kTW9kZXJhdGUkYWNjZXB0YWJsZV9kYXlzKSAjIGFjY2VwdGFibGUgZGF5cyBzdCBkZXYKCiMgZmluZCBhbnkgdmFsdWVzIGluIGdvb2QgZGF5cyB0aGF0IGFyZSAzIG9yIG1vcmUgc3RhbmRhcmQgZGV2aWF0aW9ucyBmcm9tIHRoZSBtZWFuCmFsbERhdGFTdW1Hb29kTW9kZXJhdGUkc3REZXZGcm9tTWVhbiA8LSAoKGFsbERhdGFTdW1Hb29kTW9kZXJhdGUkYWNjZXB0YWJsZV9kYXlzIC0gYWNjZXB0YWJsZURheXNNZWFuKSAvIGFjY2VwdGFibGVEYXlzU3REZXYpCgphbGxEYXRhU3VtR29vZE1vZGVyYXRlW3doaWNoKGFicyhhbGxEYXRhU3VtR29vZE1vZGVyYXRlJHN0RGV2RnJvbU1lYW4pID49IDMpLF0KCiMgb21pdCB0aGUgc3RhbmRhcmQgZGV2IGZyb20gbWVhbiBjb2x1bW5zIGZvciBtb2RlbCBjcmVhdGlvbgphbGxEYXRhU3VtR29vZE1vZGVyYXRlIDwtIGFsbERhdGFTdW1Hb29kTW9kZXJhdGVbLC05XQpgYGAKPHA+IEEgaGlzdG9ncmFtIHdhcyB0aGVuIHVzZWQgdG8gc2VlIHRoZSBkaXN0cmlidXRpb24gb2YgdGhlIHJlc3BvbnNlIHZhcmlhYmxlLjwvcD4KPHA+IEJlY2F1c2Ugbm9uZSBvZiB0aGUgdHJhbnNmb3JtIGhhZCBhIHNpZ25pZmljYW50IGVmZmVjdCBvbiB0aGUgZGlzdHJpYnV0aW9uIG9mIHRoZSBoZWF2aWx5IHJpZ2h0IHNrZXdlZCBkYXRhLCB0aGUgZGF0YSB3YXMgbGVmdCBhcyBpcy4gPC9wPgpgYGB7cn0KaGlzdChhbGxEYXRhU3VtR29vZE1vZGVyYXRlJGFjY2VwdGFibGVfZGF5cykKYGBgCjxwPiBUaGUgZGF0YSB3YXMgdGhlbiBvbmNlIGFnYWluIHJhbmRvbWx5IHNwbGl0IHVwIDUwLTUwIGludG8gdHJhaW5pbmcgYW5kIHRlc3QgZGF0YSBzZXRzLiA8L3A+CgpgYGB7cn0KIyB0cmFpbmluZyBkYXRhIHNldDogcmFuZG9tIGhhbGYgb2YgdGhlIGRhdGEKdHJhaW5pbmdBY2NlcHRhYmxlRGF5cyA8LSBhbGxEYXRhU3VtR29vZE1vZGVyYXRlW3NhbXBsZShucm93KGFsbERhdGFTdW1Hb29kTW9kZXJhdGUpLCBucm93KGFsbERhdGFTdW1Hb29kTW9kZXJhdGUpIC8gMiksXQoKIyB0ZXN0IGRhdGEgc2V0OiB0aGUgb3RoZXIgaGFsZiBvZiB0aGUgZGF0YQp0ZXN0QWNjZXB0YWJsZURheXMgPC0gYWxsRGF0YVN1bUdvb2RNb2RlcmF0ZVstYyhhcy5udW1lcmljKHJvd25hbWVzKHRyYWluaW5nKSkpLF0KYGBgCjxwPiBUaGVuLCB0byBjcmVhdGUgYSBtdWx0aXBsZSBsaW5lYXIgcmVncmVzc2lvbiBtb2RlbCwgYmFja3dhcmQgZml0dGluZyB3YXMgYWdhaW4gdXNlZCBhbmQgZmVhdHVyZXMgd2l0aCBwIHZhbHVlcyA+IDAuMDUgd2VyZSByZW1vdmVkIGF0IGVhY2ggc3RlcC4gPC9wPgpgYGB7cn0KIyBsaW5lYXIgcmVncmVzc2lvbiBtb2RlbCB1c2luZyB0cmFpbmluZyBkYXRhIGFuZCBiYWNrd2FyZCBmaXR0aW5nLCBvbmx5IGtlZXBpbmcgcCB2YWx1ZXMgPCAwLjA1CmFjY2VwdGFibGVEYXlzTW9kZWwgPC0gbG0oZm9ybXVsYSA9IGFjY2VwdGFibGVfZGF5cyB+IC4tKHBvdmVydHlfcGVyY2VudCttYWxlX3BlcmNlbnQpLCBkYXRhID0gdHJhaW5pbmdBY2NlcHRhYmxlRGF5cykKc3VtbWFyeShhY2NlcHRhYmxlRGF5c01vZGVsKQpgYGAKPHA+IE5leHQsIHVzaW5nIHRoZSB0ZXN0IGRhdGEgc2V0IHdhcyB1c2VkIHRvIGp1ZGdlIHRoZSBhY2N1cmFjeSBvZiB0aGUgbW9kZWwgYnkgY2FsY3VsYXRpbmcgdGhlIHByZWRpY3Rpb24gYWNjdXJhY3kgd2l0aCB0aGUgcHJlZGljdGVkIHZhbHVlcyBmb3IgdGhlIHRlc3QgZGF0YSBzZXQuIEFJQyBhbmQgQklDIGNhbGN1bGF0aW9ucyB3ZXJlIGFsc28gcGVyZm9ybWVkIGFzIHRoZXkgYXJlIGdvb2QgbWVhc3VyZXMgZm9yIG1vZGVsIHNlbGVjdGlvbiwgaW4gY2FzZSBvdGhlciBtb2RlbHMgYXJlIGNyZWF0ZWQgZm9yIHRoaXMgZGF0YS4gPC9wPgpgYGB7cn0KYWNjZXB0YWJsZURheXNQcmVkcyA8LSBwcmVkaWN0KGFjY2VwdGFibGVEYXlzTW9kZWwsIHRlc3RBY2NlcHRhYmxlRGF5cykKCiMgZGF0YSBmcmFtZSBvZiBhY3R1YWwgdmFsdWVzIHZzIHByZWRpY3RlZCB2YWx1ZXMKYWN0dWFsc19wcmVkc19hY2NlcHRhYmxlIDwtIGNiaW5kKGRhdGEuZnJhbWUoYWN0dWFscyA9IHRlc3RBY2NlcHRhYmxlRGF5cyRhY2NlcHRhYmxlX2RheXMsIHByZWRpY3RlZHMgPSBhY2NlcHRhYmxlRGF5c1ByZWRzKSkKCiMgcHJlZGljdGlvbiBhY2N1cmFjeQpjb3JyZWxhdGlvbl9hY2N1cmFjeSA8LSBjb3IoYWN0dWFsc19wcmVkc19hY2NlcHRhYmxlKQpwYXN0ZSgiUHJlZGljdGlvbiBBY2N1cmFjeTogIiwgY29ycmVsYXRpb25fYWNjdXJhY3lbMSwyXSAqIDEwMCwgIiUiLCBzZXA9IiIpCgojIGZpdCBBSUMgYW5kIEJJQyBjYWxjdWxhdGlvbnMKcGFzdGUoIkFJQzoiLCBBSUMoYWNjZXB0YWJsZURheXNNb2RlbCkpCnBhc3RlKCJCSUM6IiwgQklDKGFjY2VwdGFibGVEYXlzTW9kZWwpKQpgYGAKVXNpbmcgdGhlIHRyYWluaW5nIGFuZCB0ZXN0IGRhdGEgc2V0IHRvIGNhbGN1bGF0ZSB0aGUgcHJlZGljdGlvbiBhY2N1cmFjeSwgdGhlIG1vZGVsIHdhcyBjb3JyZWN0IGFib3V0IDI0JSBvZiB0aGUgdGltZS4KCjxoMj4gRXZhbHVsYXRpb24gb2YgTW9kZWxzIDwvaDI+Cgo8aDQ+IE1vZGVsIDE6IEdvb2QgRGF5cyBhcyBSZXNwb25zZSBWYXJpYWJsZSA8L2g0Pgo8cD4gT3ZlcmFsbCwgdGhpcyBtb2RlbCBpcyBub3QgYSB2ZXJ5IGdvb2QgbWVhc3VyZSBmb3IgcHJlZGljdGluZyB0aGUgcGVyY2VudGFnZSBvZiBnb29kIGFpciBxdWFsaXR5IGRheXMgYSBsb2NhdGlvbiB3aWxsIGhhdmUuIEJvdGggdGhlIG11bHRpcGxlIFIgc3F1YXJlZCBhbmQgdGhlIGFkanVzdGVkIFIgc3F1YXJlZCBhcmUgdmVyeSBsb3cgYW5kIGFyZSBzaWduaWZpY2FudGx5IGJlbG93IDAuNy4gVGhpcyBtZWFucyB0aGF0IHRoZSB2YXJpYXRpb24gb2YgZ29vZCBhaXIgcXVhbGl0eSBkYXlzIGlzIG5vdCBzaWduaWZpY2FudGx5IGV4cGxhaW5lZCBieSB0aGlzIG1vZGVsLiBUaGUgY2FsY3VsYXRlZCBNQUQgZm9yIHRoaXMgbW9kZWwgaXMgMjguNjYgd2hpY2ggaXMgbm90IHZlcnkgY2xvc2UgdG8gMCwgbWVhbmluZyB0aGVyZSBpcyBhIHNpZ25pZmljYW50IGFtb3VudCBvZiBkZXZpYXRpb24gaW4gdGhpcyBtb2RlbC4gVGhlIG1vZGVsIGlzIHN0YXRpc3RpY2FsbHkgc2lnbmlmaWNhbnQgYmVjYXVzZSB0aGUgcC12YWx1ZXMgZm9yIGVhY2ggZmVhdHVyZSBhcyB3ZWxsIGFzIHRoZSBvdmVyYWxsIG1vZGVsIHAtdmFsdWUgYXJlIHdlbGwgYmVsb3cgMC4wNS4KCjxoND4gTW9kZWwgMjogR29vZCtNb2RlcmF0ZSBEYXlzIGFzIFJlc3BvbnNlIFZhcmlhYmxlIDwvaDQ+CjxwPiBUaGlzIG1vZGVsIGlzIGFsc28gbm90IGEgZ3JlYXQgbWVhc3VyZSBmb3IgcHJlZGljdGluZyB0aGUgcGVyY2VudGFnZSBvZiBhY2NlcHRhYmxlIGRheXMgKHRoYXQgaXMsIGdvb2QgYW5kIG1vZGVyYXRlIGRheXMpLiBUaGUgbXVsdGlwbGUgUiBzcXVhcmVkIGFuZCB0aGUgYWRqdXN0ZWQgUiBzcWF1cmVzIGFyZSBldmVuIGxvd2VyIGluIHRoaXMgbW9kZWwgdGhhbiB0aGUgcHJldmlvdXMgbW9kZWwuIFRoaXMgbWVhbnMgdGhhdCBldmVuIGxlc3MgdmFyaWF0aW9uIGluIHRoZSBhY2NlcHRhYmxlIGFpciBxdWFsaXR5IGRheXMgaXMgZGVzY3JpYmVkIGJ5IHRoaXMgbW9kZWwgdGhhbiB0aGUgcHJldmlvdXMgbW9kZWwuIFRoZSBjYWxjdWxhdGVkIE1BRCBmb3IgdGhpcyBtb2RlbCwgaG93ZXZlciwgaXMgMC4wNzQsIHdoaWNoIGlzIG11Y2ggY2xvc2VyIHRvIDAgdGhhbiB0aGUgcHJldmlvdXMgbW9kZWwuIE1lYW5pbmcgdGhhdCB0aGVyZSBpcyBtdWNoIGxlc3MgZGV2aWF0aW9uIGluIHRoaXMgbW9kZWwgdGhhbiB0aGUgcHJldmlvdXMgbW9kZWwuIFRoaXMgbWFrZXMgc2Vuc2UgYmVjYXVzZSBvdXRzaWRlIG9mIHRoZSAxNSBvdXRsaWVycywgdGhlIHBlcmNlbnRhZ2Ugb2YgYWNjZXB0YWJsZSBkYXlzIHdhcyBtb3N0bHkgYXJvdW5kIDkwJS4gVGhpcyBtb2RlbCBpcyBhbHNvIHN0YXRpc3RpY2FsbHkgc2lnbmlmaWNhbnQgd2l0aCBvdmVyYWxsIGFuZCBpbmRpdmlkdWFsIGZlYXR1cmUgcC12YWx1ZXMgd2VsbCBiZWxvdyAwLjA1LiBUaGUgQUlDIGFuZCBCSUMgb2YgdGhpcyBtb2RlbCBhcmUgYWxzbyBtdWNoIGxvd2VyIHRoYW4gdGhlIHByZXZpb3VzIG1vZGVsLCBtZWFuaW5nIHRoaXMgbW9kZWwgaXMgbW9yZSBsaWtlbHkgdG8gYmUgdGhlIHRydWUgbW9kZWwgYW5kIGlzIGNsb3NlciB0byB0aGUgdHJ1dGguPC9wPgoKPGgyPiBDb25jbHVzaW9uIDwvaDI+CjxwPiBPdmVyYWxsLCBJIGh5cG90aGVzaXplZCB0aGF0IGJ1aWxkaW5nIGEgbW9kZWwgd2l0aCB0aGVzZSBhYm92ZSBkZW1vZ3JhcGhpYyBmZWF0dXJlcyB3b3VsZCBiZSBhIGdvb2QgcHJlZGljdG9yIGZvciBhaXIgcXVhbGl0eSBpcyBVUyBsb2NhdGlvbnMuIEhvd2V2ZXIsIGZyb20gdGhlIHR3byBtb2RlbHMgSSBoYXZlIGJ1aWx0IGFuZCB0aGUgZGF0YSBJIGFnZ3JlZ2F0ZWQsIHRoaXMgZG9lcyBub3Qgc2VlbSB0byBiZSB0aGUgY2FzZS4gPC9wPgo8cD4gVGhlcmUgYXJlIGEgZmV3IHJlYXNvbnMgd2h5IHRoaXMgd2FzIHRoZSByZXN1bHQ6IDwvcD4KPHVsPgogIDxsaT4gPHA+IFRoZXJlIGlzIG5vdCBlbm91Z2ggYWlyIHF1YWxpdHkgZGF0YS4gV2hpbGUgdGhlcmUgd2VyZSBhcm91bmQgMywwMDAgcmVjb3JkcyBmcm9tIHRoZSBjZW5zdXMgZGVtb2dyYXBoaWMgZGF0YSwgdGhlcmUgd2VyZSBvbmx5IGFib3V0IDEsMDAwIHJlY29yZHMgZm9yIHRoZSBhaXIgcXVhbGl0eSBkYXRhLiBUaGlzIG1lYW5zIHRoYXQgYXJlYXMgdGhhdCBjb3VsZCBoYXZlIGNvbnRyaWJ1dGVkIHRvIGEgbW9yZSBzdWNjZXNzZnVsIG1vZGVsIGRpZCBub3QgaGF2ZSBhaXIgcXVhbGl0eSByZWNvcmRlZC4gPC9wPiA8L2xpPgogIDxsaT4gPHA+VGhlIGNvcnJlbGF0aW9uIGFjdHVhbGx5IGRvZXMgbm90IGV4aXN0LiBJIGRvIG5vdCBiZWxpZXZlIHRoaXMgaXMgdGhlIGNhc2UsIGJlY2F1c2UgaXQgaGFzIGJlZW4gc2hvd24gdGhhdCB0aGUgY29ycmVsYXRpb24gZXhpc3RzIGZvciB3YXRlciBxdWFsaXR5IGFuZCB0aGUgbWFueSBlbnZpcm9ubWVudGFsIHJhY2lzbSBjYXNlcyBpbiB0aGUgVVMuPC9wPjwvbGk+CiAgPGxpPiA8cD5UaGUgMjAxMCBkYXRhIGlzIHRvbyBvbGQgYW5kIGluIDIwMTAsIGFpciBxdWFsaXR5IHdhcyBub3QgZGVwZW5kZW50IG9uIGFueSBkZW1vZ3JhcGhpY3MuIEkgZG9uJ3QsIGhvd2V2ZXIsIHRoaW5rIHRoaXMgd2FzIHRoZSBjYXNlIGFzIDIwMTAgd2FzIG9ubHkgNyB5ZWFycyBhZ28uIDwvcD48L2xpPgo8L3VsPg==